/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.neo4j.cypher.internal.parser.v5.ast.factory

import org.neo4j.cypher.internal.ast.factory.neo4j.completion.CodeCompletionCore.CandidateRule
import org.neo4j.cypher.internal.ast.factory.neo4j.completion.CodeCompletionCore.CandidatesCollection
import org.neo4j.cypher.internal.parser.CypherErrorVocabulary
import org.neo4j.cypher.internal.parser.v5.Cypher5Parser
import org.neo4j.cypher.internal.parser.v5.ast.factory.Cypher5ErrorStrategyConf
import org.neo4j.cypher.internal.util.test_helpers.CypherFunSuite

class Cypher5VocabularyTest extends CypherFunSuite {
  val conf = new Cypher5ErrorStrategyConf
  val vocab = new CypherErrorVocabulary(conf)

  test("user facing token names are human readable") {
    Range.inclusive(1, vocab.getMaxTokenType).foreach { token =>
      val displayName = vocab.getDisplayName(token)
      val symbolicName = vocab.getSymbolicName(token)
      val literalName = vocab.getLiteralName(token)
      withClue(s"displayName=$displayName, symbolicName=$symbolicName, literalName=$literalName\n") {
        expectedDisplayNames.get(token) match {
          case Some(expected) =>
            if (displayName != expected) println(token)
            displayName shouldBe expected
          case _ => fail(
              s"""Hello! I can see that you have added a new token type ($symbolicName)
                 |You need to add an expected display name in ${getClass.getName}.
                 |The reason why we fail for every new token here is that these names
                 |are user facing and we want to make sure all have a readable name.
                 |""".stripMargin
            )
        }
      }
    }
  }

  test("rules of interest have a display name") {
    conf.preferredRules.foreach { r =>
      withClue(Cypher5Parser.ruleNames(r))(ruleDisplayName(r) should not be empty)
    }
    Range(0, Cypher5Parser.ruleNames.length).foreach { r =>
      withClue(Cypher5Parser.ruleNames(r))(ruleDisplayName(r) shouldBe expectedRuleDisplayNames.get(r).toSeq)
    }
  }

  private def ruleDisplayName(r: Int): Seq[String] = {
    val candidates = new CandidatesCollection()
    candidates.rules.put(java.lang.Integer.valueOf(r), new CandidateRule(-1, java.util.List.of[java.lang.Integer]()))
    vocab.expected(candidates)
  }

  private val expectedDisplayNames: Map[Int, String] = Map(
    Cypher5Parser.SPACE -> "' '",
    Cypher5Parser.SINGLE_LINE_COMMENT -> "'//'",
    Cypher5Parser.DECIMAL_DOUBLE -> "a float value",
    Cypher5Parser.UNSIGNED_DECIMAL_INTEGER -> "an integer value",
    Cypher5Parser.UNSIGNED_HEX_INTEGER -> "a hexadecimal integer value",
    Cypher5Parser.UNSIGNED_OCTAL_INTEGER -> "an octal integer value",
    Cypher5Parser.ACCESS -> "'ACCESS'",
    Cypher5Parser.ACTIVE -> "'ACTIVE'",
    Cypher5Parser.ADMIN -> "'ADMIN'",
    Cypher5Parser.ADMINISTRATOR -> "'ADMINISTRATOR'",
    Cypher5Parser.ALIAS -> "'ALIAS'",
    Cypher5Parser.ALIASES -> "'ALIASES'",
    Cypher5Parser.ALL_SHORTEST_PATHS -> "'allShortestPaths'",
    Cypher5Parser.ALL -> "'ALL'",
    Cypher5Parser.ALTER -> "'ALTER'",
    Cypher5Parser.AND -> "'AND'",
    Cypher5Parser.ANY -> "'ANY'",
    Cypher5Parser.ARRAY -> "'ARRAY'",
    Cypher5Parser.AS -> "'AS'",
    Cypher5Parser.ASC -> "'ASC'",
    Cypher5Parser.ASCENDING -> "'ASCENDING'",
    Cypher5Parser.ASSERT -> "'ASSERT'",
    Cypher5Parser.ASSIGN -> "'ASSIGN'",
    Cypher5Parser.AT -> "'AT'",
    Cypher5Parser.AUTH -> "'AUTH'",
    Cypher5Parser.BAR -> "'|'",
    Cypher5Parser.BINDINGS -> "'BINDINGS'",
    Cypher5Parser.BOOL -> "'BOOL'",
    Cypher5Parser.BOOLEAN -> "'BOOLEAN'",
    Cypher5Parser.BOOSTED -> "'BOOSTED'",
    Cypher5Parser.BOTH -> "'BOTH'",
    Cypher5Parser.BREAK -> "'BREAK'",
    Cypher5Parser.BRIEF -> "'BRIEF'",
    Cypher5Parser.BTREE -> "'BTREE'",
    Cypher5Parser.BUILT -> "'BUILT'",
    Cypher5Parser.BY -> "'BY'",
    Cypher5Parser.CALL -> "'CALL'",
    Cypher5Parser.CASCADE -> "'CASCADE'",
    Cypher5Parser.CASE -> "'CASE'",
    Cypher5Parser.CHANGE -> "'CHANGE'",
    Cypher5Parser.CIDR -> "'CIDR'",
    Cypher5Parser.COLLECT -> "'COLLECT'",
    Cypher5Parser.COLON -> "':'",
    Cypher5Parser.COLONCOLON -> "'::'",
    Cypher5Parser.COMMA -> "','",
    Cypher5Parser.COMMAND -> "'COMMAND'",
    Cypher5Parser.COMMANDS -> "'COMMANDS'",
    Cypher5Parser.COMMIT -> "'COMMIT'",
    Cypher5Parser.COMPOSITE -> "'COMPOSITE'",
    Cypher5Parser.CONCURRENT -> "'CONCURRENT'",
    Cypher5Parser.CONSTRAINT -> "'CONSTRAINT'",
    Cypher5Parser.CONSTRAINTS -> "'CONSTRAINTS'",
    Cypher5Parser.CONTAINS -> "'CONTAINS'",
    Cypher5Parser.COPY -> "'COPY'",
    Cypher5Parser.CONTINUE -> "'CONTINUE'",
    Cypher5Parser.COUNT -> "'COUNT'",
    Cypher5Parser.CREATE -> "'CREATE'",
    Cypher5Parser.CSV -> "'CSV'",
    Cypher5Parser.CURRENT -> "'CURRENT'",
    Cypher5Parser.DATA -> "'DATA'",
    Cypher5Parser.DATABASE -> "'DATABASE'",
    Cypher5Parser.DATABASES -> "'DATABASES'",
    Cypher5Parser.DATE -> "'DATE'",
    Cypher5Parser.DATETIME -> "'DATETIME'",
    Cypher5Parser.DBMS -> "'DBMS'",
    Cypher5Parser.DEALLOCATE -> "'DEALLOCATE'",
    Cypher5Parser.DEFAULT -> "'DEFAULT'",
    Cypher5Parser.DEFINED -> "'DEFINED'",
    Cypher5Parser.DELETE -> "'DELETE'",
    Cypher5Parser.DENY -> "'DENY'",
    Cypher5Parser.DESC -> "'DESC'",
    Cypher5Parser.DESCENDING -> "'DESCENDING'",
    Cypher5Parser.DESTROY -> "'DESTROY'",
    Cypher5Parser.DETACH -> "'DETACH'",
    Cypher5Parser.DIFFERENT -> "'DIFFERENT'",
    Cypher5Parser.DOLLAR -> "'$'",
    Cypher5Parser.DISTINCT -> "'DISTINCT'",
    Cypher5Parser.DIVIDE -> "'/'",
    Cypher5Parser.DOT -> "'.'",
    Cypher5Parser.DOTDOT -> "'..'",
    Cypher5Parser.DOUBLEBAR -> "'||'",
    Cypher5Parser.DRIVER -> "'DRIVER'",
    Cypher5Parser.DROP -> "'DROP'",
    Cypher5Parser.DRYRUN -> "'DRYRUN'",
    Cypher5Parser.DUMP -> "'DUMP'",
    Cypher5Parser.DURATION -> "'DURATION'",
    Cypher5Parser.EACH -> "'EACH'",
    Cypher5Parser.EDGE -> "'EDGE'",
    Cypher5Parser.ENABLE -> "'ENABLE'",
    Cypher5Parser.ELEMENT -> "'ELEMENT'",
    Cypher5Parser.ELEMENTS -> "'ELEMENTS'",
    Cypher5Parser.ELSE -> "'ELSE'",
    Cypher5Parser.ENCRYPTED -> "'ENCRYPTED'",
    Cypher5Parser.END -> "'END'",
    Cypher5Parser.ENDS -> "'ENDS'",
    Cypher5Parser.EQ -> "'='",
    Cypher5Parser.ErrorChar -> "'ErrorChar'",
    Cypher5Parser.EXECUTABLE -> "'EXECUTABLE'",
    Cypher5Parser.EXECUTE -> "'EXECUTE'",
    Cypher5Parser.EXIST -> "'EXIST'",
    Cypher5Parser.EXISTENCE -> "'EXISTENCE'",
    Cypher5Parser.EXISTS -> "'EXISTS'",
    Cypher5Parser.ERROR -> "'ERROR'",
    Cypher5Parser.FAIL -> "'FAIL'",
    Cypher5Parser.FALSE -> "'FALSE'",
    Cypher5Parser.FIELDTERMINATOR -> "'FIELDTERMINATOR'",
    Cypher5Parser.FINISH -> "'FINISH'",
    Cypher5Parser.FLOAT -> "'FLOAT'",
    Cypher5Parser.FOR -> "'FOR'",
    Cypher5Parser.FOREACH -> "'FOREACH'",
    Cypher5Parser.FROM -> "'FROM'",
    Cypher5Parser.FULLTEXT -> "'FULLTEXT'",
    Cypher5Parser.FUNCTION -> "'FUNCTION'",
    Cypher5Parser.FUNCTIONS -> "'FUNCTIONS'",
    Cypher5Parser.GE -> "'>='",
    Cypher5Parser.GRANT -> "'GRANT'",
    Cypher5Parser.GRAPH -> "'GRAPH'",
    Cypher5Parser.GRAPHS -> "'GRAPHS'",
    Cypher5Parser.GROUP -> "'GROUP'",
    Cypher5Parser.GROUPS -> "'GROUPS'",
    Cypher5Parser.GT -> "'>'",
    Cypher5Parser.HEADERS -> "'HEADERS'",
    Cypher5Parser.HOME -> "'HOME'",
    Cypher5Parser.ID -> "'ID'",
    Cypher5Parser.IF -> "'IF'",
    Cypher5Parser.IMPERSONATE -> "'IMPERSONATE'",
    Cypher5Parser.IMMUTABLE -> "'IMMUTABLE'",
    Cypher5Parser.IN -> "'IN'",
    Cypher5Parser.INDEX -> "'INDEX'",
    Cypher5Parser.INDEXES -> "'INDEXES'",
    Cypher5Parser.INF -> "'INF'",
    Cypher5Parser.INFINITY -> "'INFINITY'",
    Cypher5Parser.INSERT -> "'INSERT'",
    Cypher5Parser.INT -> "'INT'",
    Cypher5Parser.INTEGER -> "'INTEGER'",
    Cypher5Parser.IS -> "'IS'",
    Cypher5Parser.JOIN -> "'JOIN'",
    Cypher5Parser.KEY -> "'KEY'",
    Cypher5Parser.LABEL -> "'LABEL'",
    Cypher5Parser.LABELS -> "'LABELS'",
    Cypher5Parser.AMPERSAND -> "'&'",
    Cypher5Parser.EXCLAMATION_MARK -> "'!'",
    Cypher5Parser.LBRACKET -> "'['",
    Cypher5Parser.LCURLY -> "'{'",
    Cypher5Parser.LE -> "'<='",
    Cypher5Parser.LEADING -> "'LEADING'",
    Cypher5Parser.LIMITROWS -> "'LIMIT'",
    Cypher5Parser.LIST -> "'LIST'",
    Cypher5Parser.LOAD -> "'LOAD'",
    Cypher5Parser.LOCAL -> "'LOCAL'",
    Cypher5Parser.LOOKUP -> "'LOOKUP'",
    Cypher5Parser.LPAREN -> "'('",
    Cypher5Parser.LT -> "'<'",
    Cypher5Parser.MANAGEMENT -> "'MANAGEMENT'",
    Cypher5Parser.MAP -> "'MAP'",
    Cypher5Parser.MATCH -> "'MATCH'",
    Cypher5Parser.MERGE -> "'MERGE'",
    Cypher5Parser.MINUS -> "'-'",
    Cypher5Parser.PERCENT -> "'%'",
    Cypher5Parser.INVALID_NEQ -> "'!='",
    Cypher5Parser.NEQ -> "'<>'",
    Cypher5Parser.NAME -> "'NAME'",
    Cypher5Parser.NAMES -> "'NAMES'",
    Cypher5Parser.NAN -> "'NAN'",
    Cypher5Parser.NFC -> "'NFC'",
    Cypher5Parser.NFD -> "'NFD'",
    Cypher5Parser.NFKC -> "'NFKC'",
    Cypher5Parser.NFKD -> "'NFKD'",
    Cypher5Parser.NEW -> "'NEW'",
    Cypher5Parser.NODE -> "'NODE'",
    Cypher5Parser.NODETACH -> "'NODETACH'",
    Cypher5Parser.NODES -> "'NODES'",
    Cypher5Parser.NONE -> "'NONE'",
    Cypher5Parser.NORMALIZE -> "'NORMALIZE'",
    Cypher5Parser.NORMALIZED -> "'NORMALIZED'",
    Cypher5Parser.NOT -> "'NOT'",
    Cypher5Parser.NOTHING -> "'NOTHING'",
    Cypher5Parser.NOWAIT -> "'NOWAIT'",
    Cypher5Parser.NULL -> "'NULL'",
    Cypher5Parser.OF -> "'OF'",
    Cypher5Parser.OFFSET -> "'OFFSET'",
    Cypher5Parser.ON -> "'ON'",
    Cypher5Parser.ONLY -> "'ONLY'",
    Cypher5Parser.OPTIONAL -> "'OPTIONAL'",
    Cypher5Parser.OPTIONS -> "'OPTIONS'",
    Cypher5Parser.OPTION -> "'OPTION'",
    Cypher5Parser.OR -> "'OR'",
    Cypher5Parser.ORDER -> "'ORDER'",
    Cypher5Parser.OUTPUT -> "'OUTPUT'",
    Cypher5Parser.PASSWORD -> "'PASSWORD'",
    Cypher5Parser.PASSWORDS -> "'PASSWORDS'",
    Cypher5Parser.PATH -> "'PATH'",
    Cypher5Parser.PATHS -> "'PATHS'",
    Cypher5Parser.PERIODIC -> "'PERIODIC'",
    Cypher5Parser.PLAINTEXT -> "'PLAINTEXT'",
    Cypher5Parser.PLUS -> "'+'",
    Cypher5Parser.PLUSEQUAL -> "'+='",
    Cypher5Parser.POINT -> "'POINT'",
    Cypher5Parser.POPULATED -> "'POPULATED'",
    Cypher5Parser.POW -> "'^'",
    Cypher5Parser.PRIMARY -> "'PRIMARY'",
    Cypher5Parser.PRIMARIES -> "'PRIMARIES'",
    Cypher5Parser.PRIVILEGE -> "'PRIVILEGE'",
    Cypher5Parser.PRIVILEGES -> "'PRIVILEGES'",
    Cypher5Parser.PROCEDURE -> "'PROCEDURE'",
    Cypher5Parser.PROCEDURES -> "'PROCEDURES'",
    Cypher5Parser.PROPERTIES -> "'PROPERTIES'",
    Cypher5Parser.PROPERTY -> "'PROPERTY'",
    Cypher5Parser.PROVIDER -> "'PROVIDER'",
    Cypher5Parser.PROVIDERS -> "'PROVIDERS'",
    Cypher5Parser.QUESTION -> "'?'",
    Cypher5Parser.RANGE -> "'RANGE'",
    Cypher5Parser.RBRACKET -> "']'",
    Cypher5Parser.RCURLY -> "'}'",
    Cypher5Parser.READ -> "'READ'",
    Cypher5Parser.REALLOCATE -> "'REALLOCATE'",
    Cypher5Parser.REDUCE -> "'REDUCE'",
    Cypher5Parser.RENAME -> "'RENAME'",
    Cypher5Parser.REGEQ -> "'=~'",
    Cypher5Parser.REL -> "'REL'",
    Cypher5Parser.RELATIONSHIP -> "'RELATIONSHIP'",
    Cypher5Parser.RELATIONSHIPS -> "'RELATIONSHIPS'",
    Cypher5Parser.REMOVE -> "'REMOVE'",
    Cypher5Parser.REPEATABLE -> "'REPEATABLE'",
    Cypher5Parser.REPLACE -> "'REPLACE'",
    Cypher5Parser.REPORT -> "'REPORT'",
    Cypher5Parser.REQUIRE -> "'REQUIRE'",
    Cypher5Parser.REQUIRED -> "'REQUIRED'",
    Cypher5Parser.RESTRICT -> "'RESTRICT'",
    Cypher5Parser.RETURN -> "'RETURN'",
    Cypher5Parser.REVOKE -> "'REVOKE'",
    Cypher5Parser.ROLE -> "'ROLE'",
    Cypher5Parser.ROLES -> "'ROLES'",
    Cypher5Parser.ROW -> "'ROW'",
    Cypher5Parser.ROWS -> "'ROWS'",
    Cypher5Parser.RPAREN -> "')'",
    Cypher5Parser.SCAN -> "'SCAN'",
    Cypher5Parser.SECONDARY -> "'SECONDARY'",
    Cypher5Parser.SECONDARIES -> "'SECONDARIES'",
    Cypher5Parser.SEC -> "'SEC'",
    Cypher5Parser.SECOND -> "'SECOND'",
    Cypher5Parser.SECONDS -> "'SECONDS'",
    Cypher5Parser.SEEK -> "'SEEK'",
    Cypher5Parser.SEMICOLON -> "';'",
    Cypher5Parser.SERVER -> "'SERVER'",
    Cypher5Parser.SERVERS -> "'SERVERS'",
    Cypher5Parser.SET -> "'SET'",
    Cypher5Parser.SETTING -> "'SETTING'",
    Cypher5Parser.SETTINGS -> "'SETTINGS'",
    Cypher5Parser.SHORTEST_PATH -> "'shortestPath'",
    Cypher5Parser.SHORTEST -> "'SHORTEST'",
    Cypher5Parser.SHOW -> "'SHOW'",
    Cypher5Parser.SIGNED -> "'SIGNED'",
    Cypher5Parser.SINGLE -> "'SINGLE'",
    Cypher5Parser.SKIPROWS -> "'SKIP'",
    Cypher5Parser.START -> "'START'",
    Cypher5Parser.STARTS -> "'STARTS'",
    Cypher5Parser.STATUS -> "'STATUS'",
    Cypher5Parser.STOP -> "'STOP'",
    Cypher5Parser.STRING -> "'STRING'",
    Cypher5Parser.SUPPORTED -> "'SUPPORTED'",
    Cypher5Parser.SUSPENDED -> "'SUSPENDED'",
    Cypher5Parser.TARGET -> "'TARGET'",
    Cypher5Parser.TERMINATE -> "'TERMINATE'",
    Cypher5Parser.TEXT -> "'TEXT'",
    Cypher5Parser.THEN -> "'THEN'",
    Cypher5Parser.TIME -> "'TIME'",
    Cypher5Parser.TIMES -> "'*'",
    Cypher5Parser.TIMESTAMP -> "'TIMESTAMP'",
    Cypher5Parser.TIMEZONE -> "'TIMEZONE'",
    Cypher5Parser.TO -> "'TO'",
    Cypher5Parser.TOPOLOGY -> "'TOPOLOGY'",
    Cypher5Parser.TRAILING -> "'TRAILING'",
    Cypher5Parser.TRANSACTION -> "'TRANSACTION'",
    Cypher5Parser.TRANSACTIONS -> "'TRANSACTIONS'",
    Cypher5Parser.TRAVERSE -> "'TRAVERSE'",
    Cypher5Parser.TRIM -> "'TRIM'",
    Cypher5Parser.TRUE -> "'TRUE'",
    Cypher5Parser.TYPE -> "'TYPE'",
    Cypher5Parser.TYPED -> "'TYPED'",
    Cypher5Parser.TYPES -> "'TYPES'",
    Cypher5Parser.UNION -> "'UNION'",
    Cypher5Parser.UNIQUE -> "'UNIQUE'",
    Cypher5Parser.UNIQUENESS -> "'UNIQUENESS'",
    Cypher5Parser.UNWIND -> "'UNWIND'",
    Cypher5Parser.URL -> "'URL'",
    Cypher5Parser.USE -> "'USE'",
    Cypher5Parser.USER -> "'USER'",
    Cypher5Parser.USERS -> "'USERS'",
    Cypher5Parser.USING -> "'USING'",
    Cypher5Parser.VALUE -> "'VALUE'",
    Cypher5Parser.VARCHAR -> "'VARCHAR'",
    Cypher5Parser.VECTOR -> "'VECTOR'",
    Cypher5Parser.VERBOSE -> "'VERBOSE'",
    Cypher5Parser.VERTEX -> "'VERTEX'",
    Cypher5Parser.WAIT -> "'WAIT'",
    Cypher5Parser.WHEN -> "'WHEN'",
    Cypher5Parser.WHERE -> "'WHERE'",
    Cypher5Parser.WITH -> "'WITH'",
    Cypher5Parser.WITHOUT -> "'WITHOUT'",
    Cypher5Parser.WRITE -> "'WRITE'",
    Cypher5Parser.XOR -> "'XOR'",
    Cypher5Parser.YIELD -> "'YIELD'",
    Cypher5Parser.ZONE -> "'ZONE'",
    Cypher5Parser.ZONED -> "'ZONED'",
    Cypher5Parser.IDENTIFIER -> "an identifier",
    Cypher5Parser.ARROW_LINE -> "'-'",
    Cypher5Parser.ARROW_LEFT_HEAD -> "'<'",
    Cypher5Parser.ARROW_RIGHT_HEAD -> "'>'",
    Cypher5Parser.STRING_LITERAL1 -> "a string value",
    Cypher5Parser.STRING_LITERAL2 -> "a string value",
    Cypher5Parser.MULTI_LINE_COMMENT -> "'/*'",
    Cypher5Parser.ESCAPED_SYMBOLIC_NAME -> "an identifier"
  )

  private val expectedRuleDisplayNames: Map[Int, String] = Map(
    Cypher5Parser.RULE_expression -> "an expression",
    Cypher5Parser.RULE_expression1 -> "an expression",
    Cypher5Parser.RULE_expression2 -> "an expression",
    Cypher5Parser.RULE_expression3 -> "an expression",
    Cypher5Parser.RULE_expression4 -> "an expression",
    Cypher5Parser.RULE_expression5 -> "an expression",
    Cypher5Parser.RULE_expression6 -> "an expression",
    Cypher5Parser.RULE_expression7 -> "an expression",
    Cypher5Parser.RULE_expression8 -> "an expression",
    Cypher5Parser.RULE_expression9 -> "an expression",
    Cypher5Parser.RULE_expression10 -> "an expression",
    Cypher5Parser.RULE_expression11 -> "an expression",
    Cypher5Parser.RULE_pattern -> "a graph pattern",
    Cypher5Parser.RULE_numberLiteral -> "a number",
    Cypher5Parser.RULE_parameter -> "a parameter",
    Cypher5Parser.RULE_variable -> "a variable name",
    Cypher5Parser.RULE_symbolicAliasName -> "a database name",
    Cypher5Parser.RULE_stringLiteral -> "a string",
    Cypher5Parser.RULE_symbolicNameString -> "an identifier",
    Cypher5Parser.RULE_symbolicLabelNameString -> "an identifier",
    Cypher5Parser.RULE_escapedSymbolicNameString -> "an identifier",
    Cypher5Parser.RULE_unescapedSymbolicNameString -> "an identifier",
    Cypher5Parser.RULE_unescapedLabelSymbolicNameString -> "an identifier"
  )
}
